﻿using Ionic.Zip;
using Microsoft.Win32;
using System;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Input;
using System.Xml;
using System.ComponentModel;

namespace SilverlightInjector
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private const string STR_EntryPointType = "EntryPointType";
        private const string STR_EntryPointAssembly = "EntryPointAssembly";
        private const string STR_RuntimeVersion = "RuntimeVersion";
        private const string STR_AGHikariInjectorApp = "AGHikariInjector.App";
        private const string STR_AGHikariInjector = "AGHikariInjector";
        private const string STR_AGVersion = "4.0.50401.0";
        private const string STR_SystemWindowsControlsDLL = "System.Windows.Controls.dll";
        private const string STR_SystemWindowsControlsToolkitDLL = "System.Windows.Controls.Toolkit.dll";
        private const string STR_AGHikariInjectorDLL = "AGHikariInjector.dll";
        private const string STR_SystemWindowsControlsToolkitInternalsDLL = "System.Windows.Controls.Toolkit.Internals.dll";

        private static RoutedCommand _ExitCommand = new RoutedCommand("Exit", typeof(MainWindow));
        public static RoutedCommand ExitCommand
        {
            get
            {
                return _ExitCommand;
            }
        }

        private static RoutedCommand _LoadCommand = new RoutedCommand("Load", typeof(MainWindow));
        public static RoutedCommand LoadCommand
        {
            get
            {
                return _LoadCommand;
            }
        }

        private static RoutedCommand _InspectCommand = new RoutedCommand("Inspect", typeof(MainWindow));
        public static RoutedCommand InspectCommand
        {
            get
            {
                return _InspectCommand;
            }
        }

        public MainWindow()
        {
            
            _BaseTempDir = System.IO.Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), "AGInspector\\Temp");

            if (!System.IO.Directory.Exists(_BaseTempDir))
                System.IO.Directory.CreateDirectory(_BaseTempDir);

            _FileNameToInject = GetNewPathToInject();

            InitializeComponent();

            _BgInjector = new BackgroundWorker();
            _BgInjector.WorkerReportsProgress = true;
            _BgInjector.DoWork += _BgInjector_DoWork;
            _BgInjector.ProgressChanged += _BgInjector_ProgressChanged;
            _BgInjector.RunWorkerCompleted += _BgInjector_RunWorkerCompleted;


            this.CommandBindings.Add(new CommandBinding(LoadCommand, OnLoadExecuted, OnLoadCanExecute));
            this.CommandBindings.Add(new CommandBinding(InspectCommand, OnInspectExecuted, OnInspectCanExecute));
            this.CommandBindings.Add(new CommandBinding(ExitCommand, OnExitExecuted, OnExitCanExecute));
        }

        private void OnExitCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = !_BgInjector.IsBusy;    
        }

        private void OnLoadCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = !_BgInjector.IsBusy;    
        }

        private static String GetNewPathToInject()
        {
            Random rnd = new Random();

            return System.IO.Path.Combine(_BaseTempDir, rnd.Next().ToString() + ".xap"); 
        }

        private void OnExitExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            try
            {
                System.IO.Directory.Delete(_BaseTempDir, true);
            }
            catch (Exception ex)
            {
                
            }
            Application.Current.Shutdown();
        }

        private void OnInspectCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = (!String.IsNullOrEmpty(txtAddress.Text) || System.IO.File.Exists(txtAddress.Text)) && !_BgInjector.IsBusy;
        }

        private BackgroundWorker _BgInjector;

        private void OnInspectExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            string uri = txtAddress.Text;
            if (uri.StartsWith("http:"))
            {
                WebClient client = new WebClient();
                client.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(client_DownloadFileCompleted);
                client.DownloadFileAsync(new Uri(uri), _FileNameToInject);
            }
            else
            {
                if (System.IO.File.Exists(_FileNameToInject))
                    System.IO.File.Delete(_FileNameToInject);
                System.IO.File.Copy(uri, _FileNameToInject, true);
                InjectXAP();
            }
        }

        public static readonly DependencyProperty InjectProgressProperty = DependencyProperty.Register("InjectProgress", typeof(int), typeof(MainWindow), new UIPropertyMetadata(0));
        public int InjectProgress
        {
            get
            {
                return (int)GetValue(InjectProgressProperty);
            }
            set
            {
                SetValue(InjectProgressProperty, value);
            }
        }

        public static readonly DependencyProperty ProgressVisibilityProperty = DependencyProperty.Register("ProgressVisibility", typeof(Visibility), typeof(MainWindow), new PropertyMetadata(Visibility.Collapsed));
        public Visibility ProgressVisibility
        {
            get
            {
                return (Visibility)GetValue(ProgressVisibilityProperty);
            }
            set
            {
                SetValue(ProgressVisibilityProperty, value);
            }
        }


        private void _BgInjector_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            ProgressVisibility = System.Windows.Visibility.Collapsed;
        }

        void _BgInjector_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            InjectProgress = e.ProgressPercentage;
        }

        void _BgInjector_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bgWorker = sender as BackgroundWorker;
            try
            {
                string newXapExtractedTempPath = System.IO.Path.Combine(_BaseTempDir, "New");
                string appManifestExtractedPath = System.IO.Path.Combine(newXapExtractedTempPath, "AppManifest.xaml");

                ExtractXAPFile(newXapExtractedTempPath);

                bgWorker.ReportProgress(10);

                XmlDocument doc = GetXAMLFromPath(appManifestExtractedPath);
                XmlElement depRoot = doc.DocumentElement;

                String oldEntrypointType = depRoot.GetAttribute(STR_EntryPointType);
                String oldEntrypointAsm = depRoot.GetAttribute(STR_EntryPointAssembly);

                depRoot.SetAttribute(STR_EntryPointAssembly, STR_AGHikariInjector);
                depRoot.SetAttribute(STR_EntryPointType, STR_AGHikariInjectorApp);
                depRoot.SetAttribute(STR_RuntimeVersion, STR_AGVersion);

                doc.Save(appManifestExtractedPath);

                bgWorker.ReportProgress(20);

                String toInjectDLL = GetInjectedDLLManifest(STR_AGHikariInjector, STR_AGHikariInjectorDLL);
                String toInjectSWC = GetInjectedDLLManifest("System.Windows.Controls", STR_SystemWindowsControlsDLL);
                String toInjectSWCT = GetInjectedDLLManifest("System.Windows.Controls.Toolkit", STR_SystemWindowsControlsToolkitDLL);
                String toInjectSWCTI = GetInjectedDLLManifest("System.Windows.Controls.Toolkit.Internals", STR_SystemWindowsControlsToolkitInternalsDLL);

                string appManifestFile = System.IO.File.ReadAllText(appManifestExtractedPath);

                string dOOBHeader = "<Deployment.OutOfBrowserSettings>";
                string dOOBFooter = "</Deployment.OutOfBrowserSettings>";

                if (appManifestFile.Contains(dOOBHeader))
                {
                    int indexOfStart = appManifestFile.IndexOf(dOOBHeader);
                    int indexOfEnd = appManifestFile.IndexOf(dOOBFooter) + dOOBFooter.Length;

                    appManifestFile = appManifestFile.Remove(indexOfStart, indexOfEnd - indexOfStart + 3);
                }

                string dOOBSettings = "\r\n    <OutOfBrowserSettings ShortName=\"Debug\" EnableGPUAcceleration=\"True\" ShowInstallMenuItem=\"True\">";

                string dOOBBlurb = "\r\n      <OutOfBrowserSettings.Blurb>None.</OutOfBrowserSettings.Blurb>";
                string dOOBWindow = "\r\n      <OutOfBrowserSettings.WindowSettings>\r\n        <WindowSettings Title=\"Silverlight Inspector\" />\r\n      </OutOfBrowserSettings.WindowSettings>\r\n      <OutOfBrowserSettings.SecuritySettings>";
                string dOOBSecurity = "\r\n        <SecuritySettings ElevatedPermissions=\"Required\" />\r\n      </OutOfBrowserSettings.SecuritySettings>\r\n      <OutOfBrowserSettings.Icons />\r\n    </OutOfBrowserSettings>\r\n  ";

                appManifestFile = appManifestFile.Insert(appManifestFile.IndexOf('>') + 1, "\r\n  " + dOOBHeader + dOOBSettings + dOOBBlurb + dOOBWindow + dOOBSecurity + dOOBFooter);

                bgWorker.ReportProgress(40);

                var newText = appManifestFile.Split(new String[] { "<Deployment.Parts>" }, StringSplitOptions.None).ToList();
                newText.Insert(1, "<Deployment.Parts>");
                newText.Insert(2, "\r\n" + toInjectDLL);
                if (!appManifestFile.Contains(toInjectSWC))
                    newText.Insert(3, toInjectSWC);
                if (!appManifestFile.Contains(toInjectSWCT))
                    newText.Insert(4, toInjectSWCT);
                if (!appManifestFile.Contains(toInjectSWCTI))
                    newText.Insert(5, toInjectSWCTI);

                String toOuput = "";
                foreach (var item in newText)
                {
                    toOuput += item;

                }

                bgWorker.ReportProgress(60);


                Random rnd = new Random();
                String newFile = rnd.Next().ToString() + ".xap";


                System.IO.File.WriteAllText(appManifestExtractedPath, toOuput);
                string tmpLocation = System.IO.Path.Combine(_BaseTempDir, newFile);

                bgWorker.ReportProgress(80);

                CreateNewXAP(newXapExtractedTempPath, tmpLocation);
                StartAGProcess(tmpLocation);

                _FileNameToInject = GetNewPathToInject();
                System.IO.Directory.Delete(newXapExtractedTempPath, true);

                bgWorker.ReportProgress(100);

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
                return;
            }



        }



        private void OnLoadExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = ".xap Files|*.xap";


            if (!ofd.ShowDialog().Value)
                return;

            this.txtAddress.Text = ofd.FileName;
        }

        private static string _FileNameToInject = "";
        private static string _BaseTempDir = "";
        
        private static string GetInjectedDLLManifest(String name, String source)
        {
            return String.Format("<AssemblyPart x:Name=\"{0}\" Source=\"{1}\" />", name, source);
        }


        private static void ExtractXAPFile(string path)
        {
            ZipFile file = new ZipFile(_FileNameToInject);
            file.ExtractAll(path);
            file.Dispose();
        }

        private static XmlDocument GetXAMLFromPath(string path)
        {
            var doc = new XmlDocument();
            using (XmlTextReader reader = new XmlTextReader(path))
            {
                doc.Load(reader);
                reader.Close();
            }
            return doc;
        }

        private static void AddFileToXap(ZipFile newXap, string fileToAdd)
        {
            try
            {
                newXap.AddFile(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileToAdd), "");
            }
            catch (Exception ex)
            {

            }
        }

        private static void StartAGProcess(string xapToLoad)
        {
            using (System.Diagnostics.Process proc = new System.Diagnostics.Process())
            {
                String cmdLine = String.Format("/emulate:\"{0}\" /origin:http://localhost/9999", xapToLoad);
                proc.StartInfo = new System.Diagnostics.ProcessStartInfo(System.IO.Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ProgramFilesX86), "Microsoft Silverlight\\sllauncher.exe"), cmdLine);
                proc.Start();
            }
        }

        private static void CreateNewXAP(string tempFilesPath, string xapPath)
        {
            ZipFile newXap = new ZipFile(xapPath);

            foreach (var item in System.IO.Directory.GetFiles(tempFilesPath))
            {
                AddFileToXap(newXap, item);
            }

            AddFileToXap(newXap, STR_AGHikariInjectorDLL);
            AddFileToXap(newXap, STR_SystemWindowsControlsDLL);
            AddFileToXap(newXap, STR_SystemWindowsControlsToolkitDLL);
            AddFileToXap(newXap, STR_SystemWindowsControlsToolkitInternalsDLL);

            newXap.Save();
            newXap.Dispose();
        }

        void InjectXAP()
        {
            ProgressVisibility = System.Windows.Visibility.Visible;
            _BgInjector.RunWorkerAsync(_BgInjector);
        }

        private void Window_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
                this.DragMove();
        }

        void client_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {
            InjectXAP();
        }
    }
}
